/* CsJni - .NET / Java Bridge
 * 
 * Copyright 2010 Robert E. Burke Licensed under the Apache License, Version 
 * 2.0 (the "License"); you may not use this file except in compliance with 
 * the License. You may obtain a copy of the License at 
 * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable 
 * law or agreed to in writing, software distributed under the License is 
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 
 * KIND, either express or implied. See the License for the specific language 
 * governing permissions and limitations under the License. 
*/

using System;
using System.Collections.Generic;
using System.Text;

namespace CsJni
{
    public sealed class JniString : JniObject
    {
        private static bool mStaticsInitialized = false; // TODO - review statics
        private static JniClass mStringClass;

        internal JniString(JniEnvironment environment, IntPtr handle, JniObjectLifecycle lifecycle)
            : base(environment, handle, lifecycle)
        {
        }

        public override JniConcreteObjectType ConcreteType
        {
            get { return JniConcreteObjectType.String; }
        }

        private static void FaultStatics(JniEnvironment environment)
        {
            if (!mStaticsInitialized)
            {
                mStaticsInitialized = true;
                mStringClass = (JniClass)JniClass.FindClass(environment, "java/lang/String").NewGlobalAutoReleaseAndReleaseThis(environment);
                Console.Out.WriteLine("String class: " + mStringClass.Handle); // TODO
            }
        }

        public static JniObjectArray PackStringArray(JniEnvironment environment, string[] input)
        {
            VerifyEnvironment(environment);
            VerifyInputObject(input); // OK if array is empty
            FaultStatics(environment);
            JniObjectArray array = JniObjectArray.NewObjectArray(environment, input.Length, mStringClass, null);
            for (int i = 0; i < input.Length; i++)
            {
                JniLocalFrame frame = environment.CreateLocalFrame(2);
                try
                {
                    string theString = input[i];
                    JniString sh = (theString == null) ? null : JniString.NewString(environment, theString);
                    array.SetObjectArrayElement(environment, i, sh);
                }
                finally
                {
                    frame.SafePop();
                }
            }
            return array;
        }

        public static string[] ExtractStringArray(JniEnvironment environment, JniObjectArray objectArray)
        {
            VerifyEnvironment(environment);
            VerifyInputJniObject(objectArray);
            int length = objectArray.GetArrayLength(environment);
            string[] output = new string[length];
            JniLocalFrame frame = environment.CreateLocalFrame(length);
            try
            {
                for (int i = 0; i < length; i++)
                {
                    JniObject o = objectArray.GetObjectArrayElement(environment, i);
                    output[i] = (o == null) ? null : o.ExtractStringValue(environment);
                }
            }
            finally
            {
                frame.SafePop();
            }
            return output;
        }

        public string ExtractString(JniEnvironment environment)
        {
            return ExtractStringFromHandle(environment, this.Handle);
        }

        public static string ExtractStringFromHandle(JniEnvironment environment, IntPtr handle)
        {
            VerifyEnvironment(environment);
            unsafe
            {
                int length = GetStringLengthCore(environment, handle);
                char* p = JniEnvironment.Functions.GetStringCritical(environment.Handle, handle, null);
                if (p == null)
                {
                    JniThrowable.ThrowCurrentException(environment);
                }
                try
                {
                    // IMPORTANT - do not change code between GetStringCritical/ReleaseStringCritical.
                    // See JNI documentation.
                    return new String(p, 0, length);
                }
                finally
                {
                    JniEnvironment.Functions.ReleaseStringCritical(environment.Handle, handle, p);
                    // EXCEPTION CHECK: Do nothing, this method safe to call while exception is pending, does
                    // not raise additional exceptions.
                }
            }
        }

        public static JniString NewString(
            JniEnvironment environment,
            string unicode
            )
        {
            IntPtr result = NewStringCore(environment, unicode);
            return new JniString(environment, result, JniObjectLifecycle.Local);
        }

        public static IntPtr NewStringCore(
            JniEnvironment environment,
            string unicode
            )
        {
            VerifyEnvironment(environment);
            VerifyInputObject(unicode); // OK if string is empty
            IntPtr result = JniEnvironment.Functions.NewString(environment.Handle, unicode, unicode.Length);
            if (IntPtr.Zero == result)
            {
                JniThrowable.ThrowCurrentException(environment); // null result means an exception occurred
            }
            // EXCEPTION CHECK: Do nothing, already checked for exception above.
            return result;
        }

        public int GetStringLength(
            JniEnvironment environment)
        {
            VerifyEnvironment(environment);
            return GetStringLengthCore(environment, this.Handle);
        }

        private static int GetStringLengthCore(JniEnvironment environment, IntPtr handle)
        {
            int result = JniEnvironment.Functions.GetStringLength(environment.Handle, handle);
            JniThrowable.CheckException(environment);
            return result;
        }

        public string GetStringRegion(
            JniEnvironment environment,
            int start,
            int length
            )
        {
            VerifyEnvironment(environment);
            StringBuilder buffer = new StringBuilder(length);
            JniEnvironment.Functions.GetStringRegion(environment.Handle, this.Handle, start, length, buffer);
            JniThrowable.CheckException(environment);
            return buffer.ToString();
        }

        internal static string ExtractAndRelease(JniEnvironment environment, IntPtr handle)
        {
            if (handle == IntPtr.Zero)
            {
                return null;
            }
            try
            {
                return JniString.ExtractStringFromHandle(environment, handle);
            }
            finally
            {
                JniEnvironment.Functions.DeleteLocalRef(environment.Handle, handle);
                // EXCEPTION CHECK: Do nothing, this method safe to call while exception is pending, does
                // not raise additional exceptions.
            }
        }
    }
}
